Commit ed901bc97fa12c1af56f660ebdba6d64d48ea7f5
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
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
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
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
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
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
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
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
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 | +} | ... | ... |