Commit ed901bc97fa12c1af56f660ebdba6d64d48ea7f5

Authored by puhui999
2 parents 767a26dd e01d03ee

Merge remote-tracking branch 'yudao/feature/iot' into iot

# Conflicts:
#	yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
Showing 38 changed files with 750 additions and 341 deletions
plugins/disabled.txt 0 → 100644
plugins/enabled.txt 0 → 100644
plugins/yudao-module-iot-http-plugin-2.2.0-snapshot.jar 0 → 100644
No preview for this file type
yudao-module-iot/pom.xml
... ... @@ -10,7 +10,6 @@
10 10 <modules>
11 11 <module>yudao-module-iot-api</module>
12 12 <module>yudao-module-iot-biz</module>
13   - <module>yudao-module-iot-plugin-api</module>
14 13 <module>yudao-module-iot-plugin</module>
15 14 </modules>
16 15 <modelVersion>4.0.0</modelVersion>
... ...
yudao-module-iot/yudao-module-iot-api/pom.xml
... ... @@ -21,6 +21,18 @@
21 21 <groupId>cn.iocoder.boot</groupId>
22 22 <artifactId>yudao-common</artifactId>
23 23 </dependency>
  24 + <!-- PF4J -->
  25 + <!-- TODO 芋艿:这个依赖,要不要放在 api 包 -->
  26 + <dependency>
  27 + <groupId>org.pf4j</groupId>
  28 + <artifactId>pf4j-spring</artifactId>
  29 + <exclusions>
  30 + <exclusion>
  31 + <groupId>org.slf4j</groupId>
  32 + <artifactId>slf4j-log4j12</artifactId>
  33 + </exclusion>
  34 + </exclusions>
  35 + </dependency>
24 36 </dependencies>
25 37  
26 38 </project>
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api;
  2 +
  3 +import java.util.HashMap;
  4 +import java.util.Map;
  5 +
  6 +/**
  7 + * 服务注册表 - 插架模块使用,无法使用 Spring 注入
  8 + */
  9 +public class ServiceRegistry {
  10 +
  11 + private static final Map<Class<?>, Object> services = new HashMap<>();
  12 +
  13 + /**
  14 + * 注册服务
  15 + *
  16 + * @param serviceClass 服务类
  17 + * @param serviceImpl 服务实现
  18 + * @param <T> 服务类
  19 + */
  20 + public static <T> void registerService(Class<T> serviceClass, T serviceImpl) {
  21 + services.put(serviceClass, serviceImpl);
  22 + }
  23 +
  24 + /**
  25 + * 获得服务
  26 + *
  27 + * @param serviceClass 服务类
  28 + * @param <T> 服务类
  29 + * @return 服务实现
  30 + */
  31 + @SuppressWarnings("unchecked")
  32 + public static <T> T getService(Class<T> serviceClass) {
  33 + return (T) services.get(serviceClass);
  34 + }
  35 +
  36 +}
0 37 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device;
  2 +
  3 +/**
  4 + * 设备数据 API
  5 + */
  6 +public interface DeviceDataApi {
  7 +
  8 + /**
  9 + * 保存设备数据
  10 + *
  11 + * @param productKey 产品 key
  12 + * @param deviceName 设备名称
  13 + * @param message 消息
  14 + */
  15 + void saveDeviceData(String productKey, String deviceName, String message);
  16 +
  17 +}
0 18 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/enums/product/IotProductDeviceTypeEnum.java
... ... @@ -16,7 +16,7 @@ import java.util.Arrays;
16 16 public enum IotProductDeviceTypeEnum implements IntArrayValuable {
17 17  
18 18 DIRECT(0, "直连设备"),
19   - GATEWAY_CHILD(1, "网关子设备"),
  19 + GATEWAY_SUB(1, "网关子设备"),
20 20 GATEWAY(2, "网关设备");
21 21  
22 22 /**
... ...
yudao-module-iot/yudao-module-iot-biz/pom.xml
... ... @@ -81,6 +81,13 @@
81 81 <dependency>
82 82 <groupId>org.pf4j</groupId>
83 83 <artifactId>pf4j-spring</artifactId>
  84 + <!-- TODO @芋艿:可以放到 bom 里配置 -->
  85 + <exclusions>
  86 + <exclusion>
  87 + <groupId>org.slf4j</groupId>
  88 + <artifactId>slf4j-log4j12</artifactId>
  89 + </exclusion>
  90 + </exclusions>
84 91 </dependency>
85 92  
86 93 </dependencies>
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.api.device;
  2 +
  3 +import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService;
  4 +import org.springframework.stereotype.Service;
  5 +import org.springframework.validation.annotation.Validated;
  6 +
  7 +import javax.annotation.Resource;
  8 +
  9 +/**
  10 + * 设备数据 API 实现类
  11 + */
  12 +@Service
  13 +@Validated
  14 +public class DeviceDataApiImpl implements DeviceDataApi {
  15 +
  16 + @Resource
  17 + private IotDeviceDataService deviceDataService;
  18 +
  19 + @Override
  20 + public void saveDeviceData(String productKey, String deviceName, String message) {
  21 + deviceDataService.saveDeviceData(productKey, deviceName, message);
  22 + }
  23 +
  24 +}
0 25 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/package-info.java
... ... @@ -3,4 +3,4 @@
3 3 *
4 4 * TODO 芋艿:后续删除
5 5 */
6 6 -package cn.iocoder.yudao.module.iot.api;
  7 +package cn.iocoder.yudao.module.iot.api;
7 8 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/Greetings.java deleted
1   -/*
2   - * Copyright (C) 2012-present the original author or authors.
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
17   -
18   -import cn.iocoder.yudao.module.iot.api.Greeting;
19   -import org.springframework.beans.factory.annotation.Autowired;
20   -import org.springframework.stereotype.Component;
21   -
22   -import java.util.List;
23   -
24   -/**
25   - * 打招呼 测试用例
26   - */
27   -@Component
28   -public class Greetings {
29   -
30   - @Autowired
31   - private List<Greeting> greetings;
32   -
33   - public Integer printGreetings() {
34   - System.out.printf("找到扩展点的 %d 个扩展 '%s'%n", greetings.size(), Greeting.class.getName());
35   - for (Greeting greeting : greetings) {
36   - System.out.println(">>> " + greeting.getGreeting());
37   - }
38   - return greetings.size();
39   - }
40   -
41   -}
42 0 \ No newline at end of file
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/PluginController.java
... ... @@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
3 3 import jakarta.annotation.Resource;
4 4 import org.pf4j.spring.SpringPluginManager;
5 5 import org.springframework.beans.factory.annotation.Value;
6   -import org.springframework.context.ApplicationContext;
7 6 import org.springframework.http.HttpStatus;
8 7 import org.springframework.http.ResponseEntity;
9 8 import org.springframework.web.bind.annotation.*;
... ... @@ -26,11 +25,7 @@ import java.util.stream.Collectors;
26 25 public class PluginController {
27 26  
28 27 @Resource
29   - private ApplicationContext applicationContext;
30   - @Resource
31 28 private SpringPluginManager springPluginManager;
32   - @Resource
33   - private Greetings greetings;
34 29  
35 30 @Value("${pf4j.pluginsDir}")
36 31 private String pluginsDir;
... ... @@ -73,10 +68,8 @@ public class PluginController {
73 68  
74 69 return ResponseEntity.ok("插件上传并加载成功");
75 70 } catch (IOException e) {
76   - e.printStackTrace();
77 71 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上传插件时发生错误: " + e.getMessage());
78 72 } catch (Exception e) {
79   - e.printStackTrace();
80 73 return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("加载插件时发生错误: " + e.getMessage());
81 74 }
82 75 }
... ... @@ -120,15 +113,4 @@ public class PluginController {
120 113 return ResponseEntity.ok(plugins);
121 114 }
122 115  
123   - /**
124   - * 打印问候语
125   - *
126   - * @return 问候语数量
127   - */
128   - @PermitAll
129   - @GetMapping("/printGreetings")
130   - public ResponseEntity<Integer> printGreetings() {
131   - Integer count = greetings.printGreetings();
132   - return ResponseEntity.ok(count);
133   - }
134   -}
  116 +}
135 117 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininfo/WhazzupGreeting.java deleted
1   -/*
2   - * Copyright (C) 2012-present the original author or authors.
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package cn.iocoder.yudao.module.iot.controller.admin.plugininfo;
17   -
18   -import cn.iocoder.yudao.module.iot.api.Greeting;
19   -import org.pf4j.Extension;
20   -import org.springframework.stereotype.Component;
21   -
22   -/**
23   - * 打招呼 测试用例
24   - */
25   -@Extension
26   -@Component
27   -public class WhazzupGreeting implements Greeting {
28   -
29   - @Override
30   - public String getGreeting() {
31   - return "Whazzup";
32   - }
33   -
34   -}
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceRespVO.java
... ... @@ -2,8 +2,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.*;
5   -import java.util.*;
6   -import org.springframework.format.annotation.DateTimeFormat;
7 5 import java.time.LocalDateTime;
8 6 import com.alibaba.excel.annotation.*;
9 7  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/controller/admin/plugininstance/vo/PluginInstanceSaveReqVO.java
... ... @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.iot.controller.admin.plugininstance.vo;
2 2  
3 3 import io.swagger.v3.oas.annotations.media.Schema;
4 4 import lombok.*;
5   -import java.util.*;
6 5 import jakarta.validation.constraints.*;
7 6  
8 7 @Schema(description = "管理后台 - IoT 插件实例新增/修改 Request VO")
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThinkModelMessageMapper.java renamed to yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/dal/tdengine/TdThingModelMessageMapper.java
... ... @@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
10 10 */
11 11 @Mapper
12 12 @DS("tdengine")
13   -public interface TdThinkModelMessageMapper {
  13 +public interface TdThingModelMessageMapper {
14 14  
15 15 /**
16 16 * 创建物模型消息日志超级表超级表
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/ServiceRegistryConfiguration.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.framework.plugin;
  2 +
  3 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
  4 +import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
  5 +import lombok.extern.slf4j.Slf4j;
  6 +import org.springframework.context.annotation.Bean;
  7 +import org.springframework.context.annotation.Configuration;
  8 +
  9 +import javax.annotation.PostConstruct;
  10 +import javax.annotation.Resource;
  11 +
  12 +@Slf4j
  13 +@Configuration
  14 +public class ServiceRegistryConfiguration {
  15 +
  16 + @Resource
  17 + private DeviceDataApi deviceDataApi;
  18 +
  19 + @PostConstruct
  20 + public void init() {
  21 + // 将主程序中的 DeviceDataApi 实例注册到 ServiceRegistry
  22 + ServiceRegistry.registerService(DeviceDataApi.class, deviceDataApi);
  23 + log.info("[init][将 DeviceDataApi 实例注册到 ServiceRegistry 中]");
  24 + }
  25 +
  26 + /**
  27 + * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成
  28 + */
  29 + @Bean("serviceRegistryInitializedMarker") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥
  30 + public Object serviceRegistryInitializedMarker() {
  31 + // 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象
  32 + return new Object();
  33 + }
  34 +
  35 +}
0 36 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/framework/plugin/SpringConfiguration.java
1 1 package cn.iocoder.yudao.module.iot.framework.plugin;
2 2  
3   -import cn.iocoder.yudao.module.iot.controller.admin.plugininfo.Greetings;
4 3 import org.pf4j.spring.SpringPluginManager;
5 4 import org.springframework.context.annotation.Bean;
6 5 import org.springframework.context.annotation.Configuration;
... ... @@ -10,14 +9,9 @@ import org.springframework.context.annotation.DependsOn;
10 9 public class SpringConfiguration {
11 10  
12 11 @Bean
  12 + @DependsOn("serviceRegistryInitializedMarker")
13 13 public SpringPluginManager pluginManager() {
14 14 return new SpringPluginManager();
15 15 }
16 16  
17   - @Bean
18   - @DependsOn("pluginManager")
19   - public Greetings greetings() {
20   - return new Greetings();
21   - }
22   -
23 17 }
24 18 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/mq/consumer/simulatesend/SimulateSendConsumer.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.mq.consumer.simulatesend;
  2 +
  3 +/**
  4 + * TODO @alwayssuper:记得实现,还有类注释哈
  5 + *
  6 + * @author alwayssuper
  7 + * @since 2024/12/20 8:04
  8 + */
  9 +public class SimulateSendConsumer {
  10 +}
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
... ... @@ -20,7 +20,8 @@ public interface IotDeviceDataService {
20 20 *
21 21 * @param productKey 产品 key
22 22 * @param deviceName 设备名称
23   - * @param message 消息
  23 + * @param message 消息
  24 + * <p>参见 <a href="https://help.aliyun.com/zh/iot/user-guide/device-properties-events-and-services?spm=a2c4g.11186623.0.0.3a3335aeUdzkz2#concept-mvc-4tw-y2b">JSON 格式</a>
24 25 */
25 26 void saveDeviceData(String productKey, String deviceName, String message);
26 27  
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininfo/PluginInfoServiceImpl.java
... ... @@ -22,9 +22,7 @@ import org.springframework.stereotype.Service;
22 22 import org.springframework.validation.annotation.Validated;
23 23 import org.springframework.web.multipart.MultipartFile;
24 24  
25   -import java.nio.file.Files;
26 25 import java.nio.file.Path;
27   -import java.nio.file.Paths;
28 26 import java.util.concurrent.Executors;
29 27 import java.util.concurrent.TimeUnit;
30 28 import java.util.jar.JarEntry;
... ... @@ -220,24 +218,24 @@ public class PluginInfoServiceImpl implements PluginInfoService {
220 218 pluginInfoMapper.updateById(pluginInfoDo);
221 219 }
222 220  
223   - @PostConstruct
224   - public void init() {
225   - Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
226   - }
227   -
228   - @SneakyThrows
229   - private void startPlugins() {
230   - for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
231   - if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
232   - continue;
233   - }
234   - log.info("start plugin:{}", pluginInfoDO.getPluginId());
235   - try {
236   - pluginManager.startPlugin(pluginInfoDO.getPluginId());
237   - } catch (Exception e) {
238   - log.error("start plugin error", e);
239   - }
240   - }
241   - }
  221 +// @PostConstruct
  222 +// public void init() {
  223 +// Executors.newSingleThreadScheduledExecutor().schedule(this::startPlugins, 3, TimeUnit.SECONDS);
  224 +// }
  225 +//
  226 +// @SneakyThrows
  227 +// private void startPlugins() {
  228 +// for (PluginInfoDO pluginInfoDO : pluginInfoMapper.selectList()) {
  229 +// if (!IotPluginStatusEnum.RUNNING.getStatus().equals(pluginInfoDO.getStatus())) {
  230 +// continue;
  231 +// }
  232 +// log.info("start plugin:{}", pluginInfoDO.getPluginId());
  233 +// try {
  234 +// pluginManager.startPlugin(pluginInfoDO.getPluginId());
  235 +// } catch (Exception e) {
  236 +// log.error("start plugin error", e);
  237 +// }
  238 +// }
  239 +// }
242 240  
243 241 }
244 242 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/plugininstance/PluginInstanceServiceImpl.java
... ... @@ -13,6 +13,7 @@ import org.springframework.validation.annotation.Validated;
13 13 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
14 14 import static cn.iocoder.yudao.module.iot.enums.ErrorCodeConstants.PLUGIN_INSTANCE_NOT_EXISTS;
15 15  
  16 +// TODO @haohao:可以搞个 plugin 包,然后把 plugininfo、plugininstance
16 17 /**
17 18 * IoT 插件实例 Service 实现类
18 19 *
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/tdengine/IotThingModelMessageServiceImpl.java
... ... @@ -7,19 +7,21 @@ import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
7 7 import cn.iocoder.yudao.module.iot.controller.admin.device.vo.device.IotDeviceStatusUpdateReqVO;
8 8 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDO;
9 9 import cn.iocoder.yudao.module.iot.dal.dataobject.device.IotDeviceDataDO;
10   -import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
11   -import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.*;
  10 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.FieldParser;
  11 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdFieldDO;
  12 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.TdTableDO;
  13 +import cn.iocoder.yudao.module.iot.dal.dataobject.tdengine.ThingModelMessage;
12 14 import cn.iocoder.yudao.module.iot.dal.dataobject.thingmodel.IotProductThingModelDO;
  15 +import cn.iocoder.yudao.module.iot.dal.dataobject.product.IotProductDO;
13 16 import cn.iocoder.yudao.module.iot.dal.redis.deviceData.DeviceDataRedisDAO;
14 17 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDDLMapper;
15 18 import cn.iocoder.yudao.module.iot.dal.tdengine.TdEngineDMLMapper;
16   -import cn.iocoder.yudao.module.iot.dal.tdengine.TdThinkModelMessageMapper;
17 19 import cn.iocoder.yudao.module.iot.enums.IotConstants;
18 20 import cn.iocoder.yudao.module.iot.enums.device.IotDeviceStatusEnum;
19 21 import cn.iocoder.yudao.module.iot.enums.thingmodel.IotProductThingModelTypeEnum;
20 22 import cn.iocoder.yudao.module.iot.service.device.IotDeviceService;
21   -import cn.iocoder.yudao.module.iot.service.product.IotProductService;
22 23 import cn.iocoder.yudao.module.iot.service.thingmodel.IotProductThingModelService;
  24 +import cn.iocoder.yudao.module.iot.service.product.IotProductService;
23 25 import cn.iocoder.yudao.module.iot.util.IotTdDatabaseUtils;
24 26 import jakarta.annotation.Resource;
25 27 import lombok.extern.slf4j.Slf4j;
... ... @@ -64,11 +66,6 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
64 66 @Resource
65 67 private DeviceDataRedisDAO deviceDataRedisDAO;
66 68  
67   - @Resource
68   - private IotTdDatabaseUtils iotTdDatabaseUtils;
69   -
70   - @Resource
71   - private TdThinkModelMessageMapper tdThinkModelMessageMapper;
72 69  
73 70 // TODO @haohao:这个方法,可以考虑加下 1. 2. 3. 更有层次感
74 71 @Override
... ... @@ -81,7 +78,7 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
81 78 iotDeviceService.updateDeviceStatus(new IotDeviceStatusUpdateReqVO()
82 79 .setId(device.getId()).setStatus(IotDeviceStatusEnum.ONLINE.getStatus()));
83 80 // 1.2 创建物模型日志设备表
84   - createThinkModelMessageDeviceTable(device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
  81 + createThingModelMessageDeviceTable(device.getProductKey(), device.getDeviceName(), device.getDeviceKey());
85 82 }
86 83  
87 84 // 2. 获取设备属性并进行物模型校验,过滤非物模型属性
... ... @@ -118,13 +115,23 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
118 115  
119 116 // 2. 获取超级表的名称和数据库名称
120 117 // TODO @alwayssuper:最好 databaseName、superTableName 的处理,放到 tdThinkModelMessageMapper 里。可以考虑,弄个 default 方法
121   - String databaseName = iotTdDatabaseUtils.getDatabaseName();
  118 + String databaseName = IotTdDatabaseUtils.getDatabaseName(url);
122 119 String superTableName = IotTdDatabaseUtils.getThingModelMessageSuperTableName(product.getProductKey());
123 120  
  121 + // 解析物模型,获取字段列表
  122 + List<TdFieldDO> schemaFields = List.of(
  123 + TdFieldDO.builder().fieldName("time").dataType("TIMESTAMP").build(),
  124 + TdFieldDO.builder().fieldName("id").dataType("NCHAR").dataLength(64).build(),
  125 + TdFieldDO.builder().fieldName("sys").dataType("NCHAR").dataLength(2048).build(),
  126 + TdFieldDO.builder().fieldName("method").dataType("NCHAR").dataLength(256).build(),
  127 + TdFieldDO.builder().fieldName("params").dataType("NCHAR").dataLength(2048).build()
  128 + );
  129 + // 设置超级表的标签
  130 + List<TdFieldDO> tagsFields = List.of(
  131 + TdFieldDO.builder().fieldName("device_key").dataType("NCHAR").dataLength(64).build()
  132 + );
124 133 // 3. 创建超级表
125   - tdThinkModelMessageMapper.createSuperTable(ThingModelMessageDO.builder().build()
126   - .setDataBaseName(databaseName)
127   - .setSuperTableName(superTableName));
  134 + tdEngineDDLMapper.createSuperTable(new TdTableDO(databaseName, superTableName, schemaFields, tagsFields));
128 135 }
129 136  
130 137 private List<IotProductThingModelDO> getValidFunctionList(String productKey) {
... ... @@ -227,21 +234,22 @@ public class IotThingModelMessageServiceImpl implements IotThingModelMessageServ
227 234 * @param productKey 产品 Key
228 235 * @param deviceName 设备名称
229 236 * @param deviceKey 设备 Key
  237 + *
230 238 */
231   - private void createThinkModelMessageDeviceTable(String productKey, String deviceName, String deviceKey) {
  239 + private void createThingModelMessageDeviceTable(String productKey, String deviceName, String deviceKey){
232 240  
233 241 // 1. 获取超级表的名称、数据库名称、设备日志表名称
234   - String databaseName = iotTdDatabaseUtils.getDatabaseName();
  242 + String databaseName = IotTdDatabaseUtils.getDatabaseName(url);
235 243 String superTableName = IotTdDatabaseUtils.getThingModelMessageSuperTableName(productKey);
236 244 // TODO @alwayssuper:最好 databaseName、superTableName、thinkModelMessageDeviceTableName 的处理,放到 tdThinkModelMessageMapper 里。可以考虑,弄个 default 方法
237   - String thinkModelMessageDeviceTableName = IotTdDatabaseUtils.getThinkModelMessageDeviceTableName(productKey, deviceName);
  245 + String thinkModelMessageDeviceTableName = IotTdDatabaseUtils.getThingModelMessageDeviceTableName(productKey, deviceName);
238 246  
239 247 // 2. 创建物模型日志设备数据表
240   - tdThinkModelMessageMapper.createTableWithTag(ThingModelMessageDO.builder().build()
241   - .setDataBaseName(databaseName)
242   - .setSuperTableName(superTableName)
243   - .setTableName(thinkModelMessageDeviceTableName)
244   - .setDeviceKey(deviceKey));
  248 +// tdThingModelMessageMapper.createTableWithTag(ThingModelMessageDO.builder().build()
  249 +// .setDataBaseName(databaseName)
  250 +// .setSuperTableName(superTableName)
  251 +// .setTableName(thinkModelMessageDeviceTableName)
  252 +// .setDeviceKey(deviceKey));
245 253 }
246 254  
247 255 /**
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/util/IotTdDatabaseUtils.java
1 1 package cn.iocoder.yudao.module.iot.util;
2 2  
  3 +import cn.hutool.core.lang.Assert;
  4 +import cn.hutool.core.util.StrUtil;
3 5 import cn.iocoder.yudao.module.iot.enums.IotConstants;
4   -import org.springframework.beans.factory.annotation.Value;
5   -import org.springframework.stereotype.Component;
  6 +import cn.iocoder.yudao.module.iot.enums.product.IotProductDeviceTypeEnum;
6 7  
7 8 // TODO @芋艿:可能要思索下,有没更好的处理方式
8 9 // TODO @芋艿:怎么改成无状态
... ... @@ -11,19 +12,14 @@ import org.springframework.stereotype.Component;
11 12 *
12 13 * @author AlwaysSuper
13 14 */
14   -@Component
15 15 public class IotTdDatabaseUtils {
16 16  
17   - @Value("${spring.datasource.dynamic.datasource.tdengine.url}")
18   - private String url;
19   -
20 17 /**
21 18 * 获取数据库名称
22 19 */
23   - public String getDatabaseName() {
  20 + public static String getDatabaseName(String url) {
24 21 // TODO @alwayssuper:StrUtil.subAfter("/")
25   - int index = url.lastIndexOf("/");
26   - return index != -1 ? url.substring(index + 1) : url;
  22 + return StrUtil.subAfter(url, "/", true);
27 23 }
28 24  
29 25 /**
... ... @@ -34,12 +30,17 @@ public class IotTdDatabaseUtils {
34 30 * @return 产品超级表表名
35 31 */
36 32 public static String getProductSuperTableName(Integer deviceType, String productKey) {
37   - // TODO @alwayssuper:枚举字段,不要 1、2、3;不符合预期,抛出异常
38   - return switch (deviceType) {
39   - case 1 -> String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
40   - case 2 -> String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
41   - default -> String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
42   - };
  33 + Assert.notNull(deviceType, "deviceType 不能为空");
  34 + if (IotProductDeviceTypeEnum.GATEWAY_SUB.getType().equals(deviceType)) {
  35 + return String.format(IotConstants.GATEWAY_SUB_STABLE_NAME_FORMAT, productKey).toLowerCase();
  36 + }
  37 + if (IotProductDeviceTypeEnum.GATEWAY.getType().equals(deviceType)) {
  38 + return String.format(IotConstants.GATEWAY_STABLE_NAME_FORMAT, productKey).toLowerCase();
  39 + }
  40 + if (IotProductDeviceTypeEnum.DIRECT.getType().equals(deviceType)){
  41 + return String.format(IotConstants.DEVICE_STABLE_NAME_FORMAT, productKey).toLowerCase();
  42 + }
  43 + throw new IllegalArgumentException("deviceType 不正确");
43 44 }
44 45  
45 46 /**
... ... @@ -50,8 +51,7 @@ public class IotTdDatabaseUtils {
50 51 *
51 52 */
52 53 public static String getThingModelMessageSuperTableName(String productKey) {
53   - // TODO @alwayssuper:是不是应该 + 拼接就好,不用 format
54   - return String.format("thing_model_message_", productKey).toLowerCase();
  54 + return "thing_model_message_" + productKey.toLowerCase();
55 55 }
56 56  
57 57 /**
... ... @@ -61,8 +61,9 @@ public class IotTdDatabaseUtils {
61 61 * @param deviceName 设备名称
62 62 * @return 物模型日志设备表名
63 63 */
64   - public static String getThinkModelMessageDeviceTableName(String productKey, String deviceName) {
65   - return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT, productKey.toLowerCase(), deviceName.toLowerCase());
  64 + public static String getThingModelMessageDeviceTableName(String productKey, String deviceName) {
  65 + return String.format(IotConstants.THING_MODEL_MESSAGE_TABLE_NAME_FORMAT,
  66 + productKey.toLowerCase(), deviceName.toLowerCase());
66 67 }
67 68  
68 69 }
... ...
yudao-module-iot/yudao-module-iot-biz/src/main/resources/mapper/tdengine/TdThinkModelMessageMapper.xml
... ... @@ -2,10 +2,9 @@
2 2 <!DOCTYPE mapper
3 3 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
4 4 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
5   -<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThinkModelMessageMapper">
  5 +<mapper namespace="cn.iocoder.yudao.module.iot.dal.tdengine.TdThingModelMessageMapper">
6 6  
7 7 <!-- 创建物模型消息日志超级表 -->
8   - <!-- TODO @芋艿:捉摸下字段,特别是 sys、ts 这种缩写 -->
9 8 <update id="createSuperTable">
10 9 CREATE STABLE ${dataBaseName}.${superTableName}(
11 10 ts TIMESTAMP,
... ... @@ -14,7 +13,7 @@
14 13 method VARCHAR(255),
15 14 params VARCHAR(2048)
16 15 )TAGS (
17   - deviceKey VARCHAR(255)
  16 + device_key VARCHAR(255)
18 17 )
19 18 </update>
20 19  
... ... @@ -28,7 +27,7 @@
28 27 method ,
29 28 params
30 29 )TAGS(
31   - #{deviceKey}
  30 + #{device_key}
32 31 )
33 32 </update>
34 33 </mapper>
35 34 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin-api/pom.xml deleted
1   -<?xml version="1.0" encoding="UTF-8"?>
2   -<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3   - xmlns="http://maven.apache.org/POM/4.0.0"
4   - xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <modelVersion>4.0.0</modelVersion>
6   - <groupId>cn.iocoder.boot</groupId>
7   - <artifactId>yudao-module-iot-plugin-api</artifactId>
8   - <version>0.0.1</version>
9   - <packaging>jar</packaging>
10   -
11   - <name>${project.artifactId}</name>
12   - <description>
13   - 物联网 模块插件 API,暴露给其它模块调用
14   - </description>
15   -
16   - <properties>
17   - <pf4j-spring.version>0.9.0</pf4j-spring.version>
18   - </properties>
19   -
20   - <dependencies>
21   - <!-- PF4J Spring 集成 -->
22   - <dependency>
23   - <groupId>org.pf4j</groupId>
24   - <artifactId>pf4j-spring</artifactId>
25   - <version>${pf4j-spring.version}</version>
26   - <scope>provided</scope>
27   - </dependency>
28   - </dependencies>
29   -
30   -</project>
yudao-module-iot/yudao-module-iot-plugin-api/src/main/java/cn/iocoder/yudao/module/iot/api/Greeting.java deleted
1   -/*
2   - * Copyright (C) 2012-present the original author or authors.
3   - *
4   - * Licensed under the Apache License, Version 2.0 (the "License");
5   - * you may not use this file except in compliance with the License.
6   - * You may obtain a copy of the License at
7   - *
8   - * http://www.apache.org/licenses/LICENSE-2.0
9   - *
10   - * Unless required by applicable law or agreed to in writing, software
11   - * distributed under the License is distributed on an "AS IS" BASIS,
12   - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   - * See the License for the specific language governing permissions and
14   - * limitations under the License.
15   - */
16   -package cn.iocoder.yudao.module.iot.api;
17   -
18   -import org.pf4j.ExtensionPoint;
19   -
20   -/**
21   - * @author Decebal Suiu
22   - */
23   -public interface Greeting extends ExtensionPoint {
24   -
25   - String getGreeting();
26   -
27   -}
yudao-module-iot/yudao-module-iot-plugin/pom.xml
... ... @@ -2,19 +2,29 @@
2 2 <project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 3 xmlns="http://maven.apache.org/POM/4.0.0"
4 4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5   - <modelVersion>4.0.0</modelVersion>
6   - <groupId>cn.iocoder.boot</groupId>
7   - <artifactId>yudao-module-iot-plugin</artifactId>
8   - <version>0.0.1</version>
9   - <packaging>pom</packaging>
10   -
  5 +<!-- <modelVersion>4.0.0</modelVersion>-->
  6 +<!-- <groupId>cn.iocoder.boot</groupId>-->
  7 +<!-- <artifactId>yudao-module-iot-plugin</artifactId>-->
  8 +<!-- <version>0.0.1</version>-->
  9 +<!-- <packaging>pom</packaging>-->
  10 + <parent>
  11 + <artifactId>yudao-module-iot</artifactId>
  12 + <groupId>cn.iocoder.boot</groupId>
  13 + <version>${revision}</version>
  14 + </parent>
11 15 <modules>
  16 + <module>yudao-module-iot-demo-plugin</module>
12 17 <module>yudao-module-iot-http-plugin</module>
13 18 </modules>
14 19  
  20 + <modelVersion>4.0.0</modelVersion>
  21 +
  22 + <artifactId>yudao-module-iot-plugin</artifactId>
  23 + <packaging>pom</packaging>
  24 +
15 25 <name>${project.artifactId}</name>
16 26 <description>
17   - 物联网模块 - 插件模块
  27 + 物联网 插件 模块
18 28 </description>
19 29  
20 30 </project>
21 31 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/plugin.properties 0 → 100644
  1 +plugin.id=demo-plugin
  2 +plugin.class=cn.iocoder.yudao.module.iot.plugin.DemoPlugin
  3 +plugin.version=0.0.1
  4 +plugin.provider=ahh
  5 +plugin.dependencies=
  6 +plugin.description=demo-plugin
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/pom.xml 0 → 100644
  1 +<?xml version="1.0" encoding="UTF-8"?>
  2 +<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3 + xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
  4 + http://maven.apache.org/POM/4.0.0
  5 + http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6 + <parent>
  7 + <artifactId>yudao-module-iot-plugin</artifactId>
  8 + <groupId>cn.iocoder.boot</groupId>
  9 + <version>${revision}</version>
  10 + </parent>
  11 + <modelVersion>4.0.0</modelVersion>
  12 + <packaging>jar</packaging>
  13 +
  14 + <artifactId>yudao-module-iot-demo-plugin</artifactId>
  15 +
  16 + <name>${project.artifactId}</name>
  17 + <description>
  18 + 物联网 插件模块 - demo 插件
  19 + </description>
  20 +
  21 + <properties>
  22 + <!-- 插件相关 -->
  23 + <plugin.id>demo-plugin</plugin.id>
  24 + <plugin.class>cn.iocoder.yudao.module.iot.plugin.DemoPlugin</plugin.class>
  25 + <plugin.version>0.0.1</plugin.version>
  26 + <plugin.provider>ahh</plugin.provider>
  27 + <plugin.dependencies/>
  28 + </properties>
  29 +
  30 + <build>
  31 + <plugins>
  32 + <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
  33 + <plugin>
  34 + <groupId>org.codehaus.mojo</groupId>
  35 + <artifactId>properties-maven-plugin</artifactId>
  36 + <version>1.0-alpha-2</version>
  37 + <executions>
  38 + <execution>
  39 + <phase>initialize</phase>
  40 + <goals>
  41 + <goal>read-project-properties</goal>
  42 + </goals>
  43 + <configuration>
  44 + <files>
  45 + <file>plugin.properties</file>
  46 + </files>
  47 + </configuration>
  48 + </execution>
  49 + </executions>
  50 + </plugin>
  51 + -->
  52 +
  53 + <plugin>
  54 + <groupId>org.apache.maven.plugins</groupId>
  55 + <artifactId>maven-antrun-plugin</artifactId>
  56 + <version>1.6</version>
  57 + <executions>
  58 + <execution>
  59 + <id>unzip jar file</id>
  60 + <phase>package</phase>
  61 + <configuration>
  62 + <target>
  63 + <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" dest="target/plugin-classes" />
  64 + </target>
  65 + </configuration>
  66 + <goals>
  67 + <goal>run</goal>
  68 + </goals>
  69 + </execution>
  70 + </executions>
  71 + </plugin>
  72 +
  73 + <plugin>
  74 + <artifactId>maven-assembly-plugin</artifactId>
  75 + <version>2.3</version>
  76 + <configuration>
  77 + <descriptors>
  78 + <descriptor>
  79 + src/main/assembly/assembly.xml
  80 + </descriptor>
  81 + </descriptors>
  82 + <appendAssemblyId>false</appendAssemblyId>
  83 + </configuration>
  84 + <executions>
  85 + <execution>
  86 + <id>make-assembly</id>
  87 + <phase>package</phase>
  88 + <goals>
  89 + <goal>attached</goal>
  90 + </goals>
  91 + </execution>
  92 + </executions>
  93 + </plugin>
  94 +
  95 + <plugin>
  96 + <groupId>org.apache.maven.plugins</groupId>
  97 + <artifactId>maven-jar-plugin</artifactId>
  98 + <version>2.4</version>
  99 + <configuration>
  100 + <archive>
  101 + <manifestEntries>
  102 + <Plugin-Id>${plugin.id}</Plugin-Id>
  103 + <Plugin-Class>${plugin.class}</Plugin-Class>
  104 + <Plugin-Version>${plugin.version}</Plugin-Version>
  105 + <Plugin-Provider>${plugin.provider}</Plugin-Provider>
  106 + <Plugin-Dependencies>${plugin.dependencies}</Plugin-Dependencies>
  107 + </manifestEntries>
  108 + </archive>
  109 + </configuration>
  110 + </plugin>
  111 +
  112 + <plugin>
  113 + <artifactId>maven-deploy-plugin</artifactId>
  114 + <configuration>
  115 + <skip>true</skip>
  116 + </configuration>
  117 + </plugin>
  118 + </plugins>
  119 + </build>
  120 +
  121 + <dependencies>
  122 + <!-- 其他依赖项 -->
  123 + <dependency>
  124 + <groupId>org.springframework.boot</groupId>
  125 + <artifactId>spring-boot-starter-web</artifactId>
  126 + <version>${spring.boot.version}</version>
  127 + <scope>provided</scope>
  128 + </dependency>
  129 + <!-- PF4J Spring 集成 -->
  130 + <dependency>
  131 + <groupId>org.pf4j</groupId>
  132 + <artifactId>pf4j-spring</artifactId>
  133 + <scope>provided</scope>
  134 + </dependency>
  135 + <!-- 项目依赖 -->
  136 + <dependency>
  137 + <groupId>cn.iocoder.boot</groupId>
  138 + <artifactId>yudao-module-iot-api</artifactId>
  139 + <version>${revision}</version>
  140 + </dependency>
  141 + <dependency>
  142 + <groupId>org.projectlombok</groupId>
  143 + <artifactId>lombok</artifactId>
  144 + <version>${lombok.version}</version>
  145 + <scope>provided</scope>
  146 + </dependency>
  147 + </dependencies>
  148 +</project>
0 149 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/assembly/assembly.xml 0 → 100644
  1 +<assembly>
  2 + <id>plugin</id>
  3 + <formats>
  4 + <format>zip</format>
  5 + </formats>
  6 + <includeBaseDirectory>false</includeBaseDirectory>
  7 + <dependencySets>
  8 + <dependencySet>
  9 + <useProjectArtifact>false</useProjectArtifact>
  10 + <scope>runtime</scope>
  11 + <outputDirectory>lib</outputDirectory>
  12 + <includes>
  13 + <include>*:jar:*</include>
  14 + </includes>
  15 + </dependencySet>
  16 + </dependencySets>
  17 + <!--
  18 + <fileSets>
  19 + <fileSet>
  20 + <directory>target/classes</directory>
  21 + <outputDirectory>classes</outputDirectory>
  22 + </fileSet>
  23 + </fileSets>
  24 + -->
  25 + <fileSets>
  26 + <fileSet>
  27 + <directory>target/plugin-classes</directory>
  28 + <outputDirectory>classes</outputDirectory>
  29 + </fileSet>
  30 + </fileSets>
  31 +</assembly>
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-demo-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/DemoPlugin.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin;
  2 +
  3 +import com.sun.net.httpserver.HttpServer;
  4 +import lombok.extern.slf4j.Slf4j;
  5 +import org.pf4j.Plugin;
  6 +import org.pf4j.PluginWrapper;
  7 +import org.pf4j.RuntimeMode;
  8 +
  9 +import java.io.IOException;
  10 +import java.io.OutputStream;
  11 +import java.net.InetSocketAddress;
  12 +
  13 +/**
  14 + * 一个启动 HTTP 服务器的简单插件。
  15 + */
  16 +@Slf4j
  17 +public class DemoPlugin extends Plugin {
  18 +
  19 + private HttpServer server;
  20 +
  21 + public DemoPlugin(PluginWrapper wrapper) {
  22 + super(wrapper);
  23 + }
  24 +
  25 + @Override
  26 + public void start() {
  27 + log.info("Demo 插件启动");
  28 + // for testing the development mode
  29 + if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
  30 + log.info("DemoPlugin in DEVELOPMENT mode");
  31 + }
  32 + startDemoServer();
  33 + }
  34 +
  35 + @Override
  36 + public void stop() {
  37 + log.info("Demo 插件停止");
  38 + stopDemoServer();
  39 + }
  40 +
  41 + private void startDemoServer() {
  42 + try {
  43 + server = HttpServer.create(new InetSocketAddress(9081), 0);
  44 + server.createContext("/", exchange -> {
  45 + String response = "Hello from DemoPlugin";
  46 + exchange.sendResponseHeaders(200, response.getBytes().length);
  47 + OutputStream os = exchange.getResponseBody();
  48 + os.write(response.getBytes());
  49 + os.close();
  50 + });
  51 + server.setExecutor(null);
  52 + server.start();
  53 + log.info("HTTP 服务器启动成功,端口为 9081");
  54 + log.info("访问地址为 http://127.0.0.1:9081/");
  55 + } catch (IOException e) {
  56 + log.error("HTTP 服务器启动失败", e);
  57 + }
  58 + }
  59 +
  60 + private void stopDemoServer() {
  61 + if (server != null) {
  62 + server.stop(0);
  63 + log.info("HTTP 服务器停止成功");
  64 + }
  65 + }
  66 +
  67 +// @Extension
  68 +// public static class WelcomeGreeting implements Greeting {
  69 +//
  70 +// @Override
  71 +// public String getGreeting() {
  72 +// return "Welcome to DemoPlugin";
  73 +// }
  74 +//
  75 +// }
  76 +
  77 +}
0 78 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/plugin.properties
... ... @@ -3,3 +3,4 @@ plugin.class=cn.iocoder.yudao.module.iot.plugin.HttpPlugin
3 3 plugin.version=0.0.1
4 4 plugin.provider=ahh
5 5 plugin.dependencies=
  6 +plugin.description=http-plugin-0.0.1
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/pom.xml
... ... @@ -3,14 +3,20 @@
3 3 xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="
4 4 http://maven.apache.org/POM/4.0.0
5 5 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6 + <parent>
  7 + <artifactId>yudao-module-iot-plugin</artifactId>
  8 + <groupId>cn.iocoder.boot</groupId>
  9 + <version>${revision}</version>
  10 + </parent>
6 11 <modelVersion>4.0.0</modelVersion>
7   - <groupId>cn.iocoder.boot</groupId>
8   - <artifactId>yudao-module-iot-http-plugin</artifactId>
9   - <version>0.0.1</version>
10 12 <packaging>jar</packaging>
11 13  
  14 + <artifactId>yudao-module-iot-http-plugin</artifactId>
  15 +
12 16 <name>${project.artifactId}</name>
13   - <description>物联网 模块 - http 插件</description>
  17 + <description>
  18 + 物联网 插件模块 - http 插件
  19 + </description>
14 20  
15 21 <properties>
16 22 <!-- 插件相关 -->
... ... @@ -19,50 +25,8 @@
19 25 <plugin.version>0.0.1</plugin.version>
20 26 <plugin.provider>ahh</plugin.provider>
21 27 <plugin.dependencies/>
22   -
23   - <!-- Maven 相关 -->
24   - <java.version>17</java.version>
25   - <maven.compiler.source>${java.version}</maven.compiler.source>
26   - <maven.compiler.target>${java.version}</maven.compiler.target>
27   - <maven-antrun-plugin.version>1.6</maven-antrun-plugin.version>
28   - <maven-assembly-plugin.version>2.3</maven-assembly-plugin.version>
29   - <maven-jar-plugin.version>2.4</maven-jar-plugin.version>
30   - <pf4j-spring.version>0.9.0</pf4j-spring.version>
31   - <!-- 看看咋放到 bom 里 -->
32   - <lombok.version>1.18.34</lombok.version>
33   - <spring.boot.version>3.3.1</spring.boot.version>
34   - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
35 28 </properties>
36 29  
37   - <dependencies>
38   - <!-- 其他依赖项 -->
39   - <dependency>
40   - <groupId>org.springframework.boot</groupId>
41   - <artifactId>spring-boot-starter-web</artifactId>
42   - <version>${spring.boot.version}</version>
43   - <scope>provided</scope>
44   - </dependency>
45   - <!-- PF4J Spring 集成 -->
46   - <dependency>
47   - <groupId>org.pf4j</groupId>
48   - <artifactId>pf4j-spring</artifactId>
49   - <version>${pf4j-spring.version}</version>
50   - <scope>provided</scope>
51   - </dependency>
52   - <!-- 项目依赖 -->
53   - <dependency>
54   - <groupId>cn.iocoder.boot</groupId>
55   - <artifactId>yudao-module-iot-plugin-api</artifactId>
56   - <version>${project.version}</version>
57   - </dependency>
58   - <dependency>
59   - <groupId>org.projectlombok</groupId>
60   - <artifactId>lombok</artifactId>
61   - <version>${lombok.version}</version>
62   - <scope>provided</scope>
63   - </dependency>
64   - </dependencies>
65   -
66 30 <build>
67 31 <plugins>
68 32 <!-- DOESN'T WORK WITH MAVEN 3 (I defined the plugin metadata in properties section)
... ... @@ -89,14 +53,15 @@
89 53 <plugin>
90 54 <groupId>org.apache.maven.plugins</groupId>
91 55 <artifactId>maven-antrun-plugin</artifactId>
92   - <version>${maven-antrun-plugin.version}</version>
  56 + <version>1.6</version>
93 57 <executions>
94 58 <execution>
95 59 <id>unzip jar file</id>
96 60 <phase>package</phase>
97 61 <configuration>
98 62 <target>
99   - <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}" dest="target/plugin-classes" />
  63 + <unzip src="target/${project.artifactId}-${project.version}.${project.packaging}"
  64 + dest="target/plugin-classes"/>
100 65 </target>
101 66 </configuration>
102 67 <goals>
... ... @@ -108,7 +73,7 @@
108 73  
109 74 <plugin>
110 75 <artifactId>maven-assembly-plugin</artifactId>
111   - <version>${maven-assembly-plugin.version}</version>
  76 + <version>2.3</version>
112 77 <configuration>
113 78 <descriptors>
114 79 <descriptor>
... ... @@ -131,7 +96,7 @@
131 96 <plugin>
132 97 <groupId>org.apache.maven.plugins</groupId>
133 98 <artifactId>maven-jar-plugin</artifactId>
134   - <version>${maven-jar-plugin.version}</version>
  99 + <version>2.4</version>
135 100 <configuration>
136 101 <archive>
137 102 <manifestEntries>
... ... @@ -153,4 +118,35 @@
153 118 </plugin>
154 119 </plugins>
155 120 </build>
  121 +
  122 + <dependencies>
  123 + <!-- 其他依赖项 -->
  124 + <dependency>
  125 + <groupId>org.springframework.boot</groupId>
  126 + <artifactId>spring-boot-starter-web</artifactId>
  127 + </dependency>
  128 + <!-- PF4J Spring 集成 -->
  129 + <dependency>
  130 + <groupId>org.pf4j</groupId>
  131 + <artifactId>pf4j-spring</artifactId>
  132 + <scope>provided</scope>
  133 + </dependency>
  134 + <!-- 项目依赖 -->
  135 + <dependency>
  136 + <groupId>cn.iocoder.boot</groupId>
  137 + <artifactId>yudao-module-iot-api</artifactId>
  138 + <version>${revision}</version>
  139 + </dependency>
  140 + <dependency>
  141 + <groupId>org.projectlombok</groupId>
  142 + <artifactId>lombok</artifactId>
  143 + <version>${lombok.version}</version>
  144 + <scope>provided</scope>
  145 + </dependency>
  146 + <dependency>
  147 + <groupId>io.netty</groupId>
  148 + <artifactId>netty-all</artifactId>
  149 + <version>4.1.63.Final</version> <!-- 版本可根据需要调整 -->
  150 + </dependency>
  151 + </dependencies>
156 152 </project>
157 153 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/assembly/assembly.xml
1   -<!--
2   - Describes the plugin archive
3   -
4   - @author Decebal Suiu
5   - @version 1.0
6   --->
7 1 <assembly>
8 2 <id>plugin</id>
9 3 <formats>
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java 0 → 100644
  1 +package cn.iocoder.yudao.module.iot.plugin;
  2 +
  3 +import cn.hutool.json.JSONObject;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
  6 +import io.netty.buffer.Unpooled;
  7 +import io.netty.channel.ChannelFutureListener;
  8 +import io.netty.channel.ChannelHandlerContext;
  9 +import io.netty.channel.SimpleChannelInboundHandler;
  10 +import io.netty.handler.codec.http.*;
  11 +import io.netty.util.CharsetUtil;
  12 +
  13 +/**
  14 + * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。
  15 + *
  16 + * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post
  17 + * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段
  18 + */
  19 +public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
  20 +
  21 + private final DeviceDataApi deviceDataApi;
  22 +
  23 + public HttpHandler(DeviceDataApi deviceDataApi) {
  24 + this.deviceDataApi = deviceDataApi;
  25 + }
  26 +
  27 + @Override
  28 + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) {
  29 + // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post
  30 + // 使用 "/" 拆分路径
  31 + String uri = request.uri();
  32 + String[] parts = uri.split("/");
  33 +
  34 + /*
  35 + 拆分结果示例:
  36 + parts[0] = ""
  37 + parts[1] = "sys"
  38 + parts[2] = productKey
  39 + parts[3] = deviceName
  40 + parts[4] = "thing"
  41 + parts[5] = "event"
  42 + parts[6] = "property"
  43 + parts[7] = "post"
  44 + */
  45 + boolean isCorrectPath = parts.length == 8
  46 + && "sys".equals(parts[1])
  47 + && "thing".equals(parts[4])
  48 + && "event".equals(parts[5])
  49 + && "property".equals(parts[6])
  50 + && "post".equals(parts[7]);
  51 + if (!isCorrectPath) {
  52 + writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found");
  53 + return;
  54 + }
  55 + String productKey = parts[2];
  56 + String deviceName = parts[3];
  57 +
  58 + // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象
  59 + String requestBody = request.content().toString(CharsetUtil.UTF_8);
  60 + JSONObject jsonData;
  61 + try {
  62 + jsonData = JSONUtil.parseObj(requestBody);
  63 + } catch (Exception e) {
  64 + JSONObject res = createResponseJson(
  65 + 400,
  66 + new JSONObject(),
  67 + null,
  68 + "请求数据不是合法的 JSON 格式: " + e.getMessage(),
  69 + "thing.event.property.post",
  70 + "1.0"
  71 + );
  72 + writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString());
  73 + return;
  74 + }
  75 + String id = jsonData.getStr("id", null);
  76 +
  77 + try {
  78 + // 调用主程序的接口保存数据
  79 + deviceDataApi.saveDeviceData(productKey, deviceName, jsonData.toString());
  80 +
  81 + // 构造成功响应内容
  82 + JSONObject successRes = createResponseJson(
  83 + 200,
  84 + new JSONObject(),
  85 + id,
  86 + "success",
  87 + "thing.event.property.post",
  88 + "1.0"
  89 + );
  90 + writeResponse(ctx, HttpResponseStatus.OK, successRes.toString());
  91 + } catch (Exception e) {
  92 + JSONObject errorRes = createResponseJson(
  93 + 500,
  94 + new JSONObject(),
  95 + id,
  96 + "The format of result is error!",
  97 + "thing.event.property.post",
  98 + "1.0"
  99 + );
  100 + writeResponse(ctx, HttpResponseStatus.INTERNAL_SERVER_ERROR, errorRes.toString());
  101 + }
  102 + }
  103 +
  104 + /**
  105 + * 创建标准化的响应 JSON 对象
  106 + *
  107 + * @param code 响应状态码(业务层面的)
  108 + * @param data 返回的数据对象(JSON)
  109 + * @param id 请求的 id(可选)
  110 + * @param message 返回的提示信息
  111 + * @param method 返回的 method 标识
  112 + * @param version 返回的版本号
  113 + * @return 构造好的 JSON 对象
  114 + */
  115 + private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) {
  116 + JSONObject res = new JSONObject();
  117 + res.set("code", code);
  118 + res.set("data", data != null ? data : new JSONObject());
  119 + res.set("id", id);
  120 + res.set("message", message);
  121 + res.set("method", method);
  122 + res.set("version", version);
  123 + return res;
  124 + }
  125 +
  126 + /**
  127 + * 向客户端返回 HTTP 响应的辅助方法
  128 + *
  129 + * @param ctx 通道上下文
  130 + * @param status HTTP 响应状态码(网络层面的)
  131 + * @param content 响应内容(JSON 字符串或其他文本)
  132 + */
  133 + private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) {
  134 + // 设置响应头为 JSON 类型和正确的编码
  135 + FullHttpResponse response = new DefaultFullHttpResponse(
  136 + HttpVersion.HTTP_1_1,
  137 + status,
  138 + Unpooled.copiedBuffer(content, CharsetUtil.UTF_8)
  139 + );
  140 + response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
  141 + response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
  142 +
  143 + // 发送响应并在发送完成后关闭连接
  144 + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
  145 + }
  146 +
  147 +}
0 148 \ No newline at end of file
... ...
yudao-module-iot/yudao-module-iot-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpPlugin.java
1 1 package cn.iocoder.yudao.module.iot.plugin;
2 2  
3   -import cn.iocoder.yudao.module.iot.api.Greeting;
4   -import com.sun.net.httpserver.HttpServer;
  3 +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi;
  4 +import cn.iocoder.yudao.module.iot.api.ServiceRegistry;
  5 +import io.netty.bootstrap.ServerBootstrap;
  6 +import io.netty.channel.*;
  7 +import io.netty.channel.nio.NioEventLoopGroup;
  8 +import io.netty.channel.socket.nio.NioServerSocketChannel;
  9 +import io.netty.handler.codec.http.*;
5 10 import lombok.extern.slf4j.Slf4j;
6   -import org.apache.commons.lang.StringUtils;
7   -import org.pf4j.Extension;
8   -import org.pf4j.Plugin;
9 11 import org.pf4j.PluginWrapper;
10   -import org.pf4j.RuntimeMode;
  12 +import org.pf4j.Plugin;
11 13  
12   -import java.io.IOException;
13   -import java.io.OutputStream;
14   -import java.net.InetSocketAddress;
  14 +import java.util.concurrent.ExecutorService;
  15 +import java.util.concurrent.Executors;
15 16  
16   -/**
17   - * 一个启动 HTTP 服务器的简单插件。
18   - */
19 17 @Slf4j
20 18 public class HttpPlugin extends Plugin {
21 19  
22   - private HttpServer server;
  20 + private static final int PORT = 8092;
  21 +
  22 + private final ExecutorService executorService;
  23 + private DeviceDataApi deviceDataApi;
23 24  
24 25 public HttpPlugin(PluginWrapper wrapper) {
25 26 super(wrapper);
  27 + // 创建单线程池
  28 + this.executorService = Executors.newSingleThreadExecutor();
26 29 }
27 30  
28 31 @Override
29 32 public void start() {
30 33 log.info("HttpPlugin.start()");
31   - // for testing the development mode
32   - if (RuntimeMode.DEVELOPMENT.equals(wrapper.getRuntimeMode())) {
33   - log.info("HttpPlugin in DEVELOPMENT mode");
  34 +
  35 + // 从 ServiceRegistry 中获取主程序暴露的 DeviceDataApi 接口实例
  36 + deviceDataApi = ServiceRegistry.getService(DeviceDataApi.class);
  37 + if (deviceDataApi == null) {
  38 + log.error("未能从 ServiceRegistry 获取 DeviceDataApi 实例,请确保主程序已正确注册!");
  39 + return;
34 40 }
35   - startHttpServer();
  41 +
  42 + // 异步启动 Netty 服务器
  43 + executorService.submit(this::startHttpServer);
36 44 }
37 45  
38 46 @Override
39 47 public void stop() {
40 48 log.info("HttpPlugin.stop()");
41   - stopHttpServer();
  49 + // 停止线程池
  50 + executorService.shutdownNow();
42 51 }
43 52  
  53 + /**
  54 + * 启动 HTTP 服务
  55 + */
44 56 private void startHttpServer() {
  57 + EventLoopGroup bossGroup = new NioEventLoopGroup(1);
  58 + EventLoopGroup workerGroup = new NioEventLoopGroup();
  59 +
45 60 try {
46   - server = HttpServer.create(new InetSocketAddress(9081), 0);
47   - server.createContext("/", exchange -> {
48   - String response = "Welcome to PF4J HTTP Server";
49   - exchange.sendResponseHeaders(200, response.getBytes().length);
50   - OutputStream os = exchange.getResponseBody();
51   - os.write(response.getBytes());
52   - os.close();
53   - });
54   - server.setExecutor(null);
55   - server.start();
56   - log.info("HTTP server started on port 9081");
57   - } catch (IOException e) {
58   - log.error("Error starting HTTP server", e);
59   - }
60   - }
  61 + ServerBootstrap bootstrap = new ServerBootstrap();
  62 + bootstrap.group(bossGroup, workerGroup)
  63 + .channel(NioServerSocketChannel.class)
  64 + .childHandler(new ChannelInitializer<>() {
61 65  
62   - private void stopHttpServer() {
63   - if (server != null) {
64   - server.stop(0);
65   - log.info("HTTP server stopped");
66   - }
67   - }
  66 + @Override
  67 + protected void initChannel(Channel channel) {
  68 + channel.pipeline().addLast(new HttpServerCodec());
  69 + channel.pipeline().addLast(new HttpObjectAggregator(65536));
  70 + // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器
  71 + channel.pipeline().addLast(new HttpHandler(deviceDataApi));
  72 + }
68 73  
69   - @Extension
70   - public static class WelcomeGreeting implements Greeting {
  74 + });
71 75  
72   - @Override
73   - public String getGreeting() {
74   - return "Welcome to PF4J";
  76 + // 绑定端口并启动服务器
  77 + ChannelFuture future = bootstrap.bind(PORT).sync();
  78 + log.info("HTTP 服务器启动成功,端口为: {}", PORT);
  79 + future.channel().closeFuture().sync();
  80 + } catch (InterruptedException e) {
  81 + Thread.currentThread().interrupt();
  82 + log.error("HTTP 服务启动中断", e);
  83 + } finally {
  84 + bossGroup.shutdownGracefully();
  85 + workerGroup.shutdownGracefully();
75 86 }
76   -
77 87 }
78 88  
79   -}
80 89 \ No newline at end of file
  90 +}
... ...