Commit e998b0c7ebe0123972f9a3aaaf43aacdba0ff75d
1 parent
a2532013
【代码评审】IoT:评审 plugin 实现
Showing
11 changed files
with
42 additions
and
37 deletions
yudao-module-iot/yudao-module-iot-api/pom.xml
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/ServiceRegistry.java
| ... | ... | @@ -7,6 +7,7 @@ import java.util.Map; |
| 7 | 7 | * 服务注册表 - 插架模块使用,无法使用 Spring 注入 |
| 8 | 8 | */ |
| 9 | 9 | public class ServiceRegistry { |
| 10 | + | |
| 10 | 11 | private static final Map<Class<?>, Object> services = new HashMap<>(); |
| 11 | 12 | |
| 12 | 13 | /** |
| ... | ... | @@ -31,4 +32,5 @@ public class ServiceRegistry { |
| 31 | 32 | public static <T> T getService(Class<T> serviceClass) { |
| 32 | 33 | return (T) services.get(serviceClass); |
| 33 | 34 | } |
| 35 | + | |
| 34 | 36 | } |
| 35 | 37 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/DeviceDataApi.java renamed to yudao-module-iot/yudao-module-iot-api/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApi.java
yudao-module-iot/yudao-module-iot-biz/pom.xml
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/api/device/DeviceDataApiImpl.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.api.device; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; | |
| 4 | 3 | import cn.iocoder.yudao.module.iot.service.device.IotDeviceDataService; |
| 5 | 4 | import org.springframework.stereotype.Service; |
| 6 | 5 | import org.springframework.validation.annotation.Validated; |
| ... | ... | @@ -15,11 +14,11 @@ import javax.annotation.Resource; |
| 15 | 14 | public class DeviceDataApiImpl implements DeviceDataApi { |
| 16 | 15 | |
| 17 | 16 | @Resource |
| 18 | - private IotDeviceDataService iotDeviceDataService; | |
| 17 | + private IotDeviceDataService deviceDataService; | |
| 19 | 18 | |
| 20 | 19 | @Override |
| 21 | 20 | public void saveDeviceData(String productKey, String deviceName, String message) { |
| 22 | - iotDeviceDataService.saveDeviceData(productKey, deviceName, message); | |
| 21 | + deviceDataService.saveDeviceData(productKey, deviceName, message); | |
| 23 | 22 | } |
| 24 | 23 | |
| 25 | 24 | } |
| 26 | 25 | \ No newline at end of file | ... | ... |
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/framework/plugin/ServiceRegistryConfiguration.java
| 1 | 1 | package cn.iocoder.yudao.module.iot.framework.plugin; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; | |
| 3 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 4 | 4 | import cn.iocoder.yudao.module.iot.api.ServiceRegistry; |
| 5 | 5 | import lombok.extern.slf4j.Slf4j; |
| 6 | 6 | import org.springframework.context.annotation.Bean; |
| ... | ... | @@ -26,9 +26,10 @@ public class ServiceRegistryConfiguration { |
| 26 | 26 | /** |
| 27 | 27 | * 定义一个标记用的 Bean,用于表示 ServiceRegistry 已初始化完成 |
| 28 | 28 | */ |
| 29 | - @Bean("serviceRegistryInitializedMarker") | |
| 29 | + @Bean("serviceRegistryInitializedMarker") // TODO @haohao:1)这个名字,可以搞个 public static final 常量;2)是不是 conditionBefore 啥 | |
| 30 | 30 | public Object serviceRegistryInitializedMarker() { |
| 31 | - // 返回任意对象即可,这里返回null都可以,但最好返回个实际对象 | |
| 31 | + // 返回任意对象即可,这里返回 null 都可以,但最好返回个实际对象 | |
| 32 | 32 | return new Object(); |
| 33 | 33 | } |
| 34 | + | |
| 34 | 35 | } |
| 35 | 36 | \ No newline at end of file | ... | ... |
yudao-module-iot/yudao-module-iot-biz/src/main/java/cn/iocoder/yudao/module/iot/service/device/IotDeviceDataService.java
| ... | ... | @@ -21,7 +21,7 @@ public interface IotDeviceDataService { |
| 21 | 21 | * @param productKey 产品 key |
| 22 | 22 | * @param deviceName 设备名称 |
| 23 | 23 | * @param message 消息 |
| 24 | - * <p>JSON 格式,参见 <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">...</a> | |
| 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> | |
| 25 | 25 | */ |
| 26 | 26 | void saveDeviceData(String productKey, String deviceName, String message); |
| 27 | 27 | ... | ... |
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-plugin/yudao-module-iot-http-plugin/src/main/java/cn/iocoder/yudao/module/iot/plugin/HttpHandler.java
| ... | ... | @@ -2,7 +2,7 @@ package cn.iocoder.yudao.module.iot.plugin; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.json.JSONObject; |
| 4 | 4 | import cn.hutool.json.JSONUtil; |
| 5 | -import cn.iocoder.yudao.module.iot.api.DeviceDataApi; | |
| 5 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 6 | 6 | import io.netty.buffer.Unpooled; |
| 7 | 7 | import io.netty.channel.ChannelFutureListener; |
| 8 | 8 | import io.netty.channel.ChannelHandlerContext; |
| ... | ... | @@ -12,12 +12,9 @@ import io.netty.util.CharsetUtil; |
| 12 | 12 | |
| 13 | 13 | /** |
| 14 | 14 | * 基于 Netty 的 HTTP 处理器,用于接收设备上报的数据并调用主程序的 DeviceDataApi 接口进行处理。 |
| 15 | - * <p> | |
| 16 | - * 请求格式: | |
| 17 | - * POST /sys/{productKey}/{deviceName}/thing/event/property/post | |
| 18 | - * 请求体为 JSON 格式数据。 | |
| 19 | - * <p> | |
| 20 | - * 返回结果为 JSON 格式,包含统一的 code、data、id、message、method、version 字段。 | |
| 15 | + * | |
| 16 | + * 1. 请求格式:JSON 格式,地址为 POST /sys/{productKey}/{deviceName}/thing/event/property/post | |
| 17 | + * 2. 返回结果:JSON 格式,包含统一的 code、data、id、message、method、version 字段 | |
| 21 | 18 | */ |
| 22 | 19 | public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 23 | 20 | |
| ... | ... | @@ -29,10 +26,9 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 29 | 26 | |
| 30 | 27 | @Override |
| 31 | 28 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { |
| 32 | - String uri = request.uri(); | |
| 33 | - | |
| 34 | 29 | // 期望的路径格式: /sys/{productKey}/{deviceName}/thing/event/property/post |
| 35 | 30 | // 使用 "/" 拆分路径 |
| 31 | + String uri = request.uri(); | |
| 36 | 32 | String[] parts = uri.split("/"); |
| 37 | 33 | |
| 38 | 34 | /* |
| ... | ... | @@ -52,25 +48,19 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 52 | 48 | && "event".equals(parts[5]) |
| 53 | 49 | && "property".equals(parts[6]) |
| 54 | 50 | && "post".equals(parts[7]); |
| 55 | - | |
| 56 | 51 | if (!isCorrectPath) { |
| 57 | - // 如果路径不匹配,返回 404 错误 | |
| 58 | 52 | writeResponse(ctx, HttpResponseStatus.NOT_FOUND, "Not Found"); |
| 59 | 53 | return; |
| 60 | 54 | } |
| 61 | - | |
| 62 | 55 | String productKey = parts[2]; |
| 63 | 56 | String deviceName = parts[3]; |
| 64 | 57 | |
| 65 | - // 从请求中获取原始数据 | |
| 58 | + // 从请求中获取原始数据,尝试解析请求数据为 JSON 对象 | |
| 66 | 59 | String requestBody = request.content().toString(CharsetUtil.UTF_8); |
| 67 | - | |
| 68 | - // 尝试解析请求数据为 JSON 对象 | |
| 69 | 60 | JSONObject jsonData; |
| 70 | 61 | try { |
| 71 | 62 | jsonData = JSONUtil.parseObj(requestBody); |
| 72 | 63 | } catch (Exception e) { |
| 73 | - // 数据不是合法的 JSON 格式,返回 400 错误 | |
| 74 | 64 | JSONObject res = createResponseJson( |
| 75 | 65 | 400, |
| 76 | 66 | new JSONObject(), |
| ... | ... | @@ -82,8 +72,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 82 | 72 | writeResponse(ctx, HttpResponseStatus.BAD_REQUEST, res.toString()); |
| 83 | 73 | return; |
| 84 | 74 | } |
| 85 | - | |
| 86 | - // 获取请求中的 id 字段,若不存在则为 null | |
| 87 | 75 | String id = jsonData.getStr("id", null); |
| 88 | 76 | |
| 89 | 77 | try { |
| ... | ... | @@ -101,7 +89,6 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 101 | 89 | ); |
| 102 | 90 | writeResponse(ctx, HttpResponseStatus.OK, successRes.toString()); |
| 103 | 91 | } catch (Exception e) { |
| 104 | - // 保存数据过程中出现异常,返回 500 错误 | |
| 105 | 92 | JSONObject errorRes = createResponseJson( |
| 106 | 93 | 500, |
| 107 | 94 | new JSONObject(), |
| ... | ... | @@ -128,7 +115,7 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 128 | 115 | private JSONObject createResponseJson(int code, JSONObject data, String id, String message, String method, String version) { |
| 129 | 116 | JSONObject res = new JSONObject(); |
| 130 | 117 | res.set("code", code); |
| 131 | - res.set("data", data != null ? data : new JSONObject()); // 确保 data 不为 null | |
| 118 | + res.set("data", data != null ? data : new JSONObject()); | |
| 132 | 119 | res.set("id", id); |
| 133 | 120 | res.set("message", message); |
| 134 | 121 | res.set("method", method); |
| ... | ... | @@ -137,24 +124,24 @@ public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { |
| 137 | 124 | } |
| 138 | 125 | |
| 139 | 126 | /** |
| 140 | - * 向客户端返回 HTTP 响应的辅助方法。 | |
| 127 | + * 向客户端返回 HTTP 响应的辅助方法 | |
| 141 | 128 | * |
| 142 | 129 | * @param ctx 通道上下文 |
| 143 | 130 | * @param status HTTP 响应状态码(网络层面的) |
| 144 | 131 | * @param content 响应内容(JSON 字符串或其他文本) |
| 145 | 132 | */ |
| 146 | 133 | private void writeResponse(ChannelHandlerContext ctx, HttpResponseStatus status, String content) { |
| 134 | + // 设置响应头为 JSON 类型和正确的编码 | |
| 147 | 135 | FullHttpResponse response = new DefaultFullHttpResponse( |
| 148 | 136 | HttpVersion.HTTP_1_1, |
| 149 | 137 | status, |
| 150 | 138 | Unpooled.copiedBuffer(content, CharsetUtil.UTF_8) |
| 151 | 139 | ); |
| 152 | - | |
| 153 | - // 设置响应头为 JSON 类型和正确的编码 | |
| 154 | 140 | response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8"); |
| 155 | 141 | response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); |
| 156 | 142 | |
| 157 | 143 | // 发送响应并在发送完成后关闭连接 |
| 158 | 144 | ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); |
| 159 | 145 | } |
| 146 | + | |
| 160 | 147 | } |
| 161 | 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.DeviceDataApi; | |
| 3 | +import cn.iocoder.yudao.module.iot.api.device.DeviceDataApi; | |
| 4 | 4 | import cn.iocoder.yudao.module.iot.api.ServiceRegistry; |
| 5 | 5 | import io.netty.bootstrap.ServerBootstrap; |
| 6 | 6 | import io.netty.channel.*; |
| ... | ... | @@ -18,12 +18,14 @@ import java.util.concurrent.Executors; |
| 18 | 18 | public class HttpPlugin extends Plugin { |
| 19 | 19 | |
| 20 | 20 | private static final int PORT = 8092; |
| 21 | + | |
| 21 | 22 | private final ExecutorService executorService; |
| 22 | - private DeviceDataApi deviceDataApi; // 用于保存设备数据的 API | |
| 23 | + private DeviceDataApi deviceDataApi; | |
| 23 | 24 | |
| 24 | 25 | public HttpPlugin(PluginWrapper wrapper) { |
| 25 | 26 | super(wrapper); |
| 26 | - this.executorService = Executors.newSingleThreadExecutor(); // 创建单线程池 | |
| 27 | + // 创建单线程池 | |
| 28 | + this.executorService = Executors.newSingleThreadExecutor(); | |
| 27 | 29 | } |
| 28 | 30 | |
| 29 | 31 | @Override |
| ... | ... | @@ -44,10 +46,13 @@ public class HttpPlugin extends Plugin { |
| 44 | 46 | @Override |
| 45 | 47 | public void stop() { |
| 46 | 48 | log.info("HttpPlugin.stop()"); |
| 47 | - executorService.shutdownNow(); // 停止线程池 | |
| 49 | + // 停止线程池 | |
| 50 | + executorService.shutdownNow(); | |
| 48 | 51 | } |
| 49 | 52 | |
| 50 | - // 启动 HTTP 服务 | |
| 53 | + /** | |
| 54 | + * 启动 HTTP 服务 | |
| 55 | + */ | |
| 51 | 56 | private void startHttpServer() { |
| 52 | 57 | EventLoopGroup bossGroup = new NioEventLoopGroup(1); |
| 53 | 58 | EventLoopGroup workerGroup = new NioEventLoopGroup(); |
| ... | ... | @@ -56,7 +61,8 @@ public class HttpPlugin extends Plugin { |
| 56 | 61 | ServerBootstrap bootstrap = new ServerBootstrap(); |
| 57 | 62 | bootstrap.group(bossGroup, workerGroup) |
| 58 | 63 | .channel(NioServerSocketChannel.class) |
| 59 | - .childHandler(new ChannelInitializer<Channel>() { | |
| 64 | + .childHandler(new ChannelInitializer<>() { | |
| 65 | + | |
| 60 | 66 | @Override |
| 61 | 67 | protected void initChannel(Channel channel) { |
| 62 | 68 | channel.pipeline().addLast(new HttpServerCodec()); |
| ... | ... | @@ -64,6 +70,7 @@ public class HttpPlugin extends Plugin { |
| 64 | 70 | // 将从 ServiceRegistry 获取的 deviceDataApi 传入处理器 |
| 65 | 71 | channel.pipeline().addLast(new HttpHandler(deviceDataApi)); |
| 66 | 72 | } |
| 73 | + | |
| 67 | 74 | }); |
| 68 | 75 | |
| 69 | 76 | // 绑定端口并启动服务器 |
| ... | ... | @@ -78,4 +85,5 @@ public class HttpPlugin extends Plugin { |
| 78 | 85 | workerGroup.shutdownGracefully(); |
| 79 | 86 | } |
| 80 | 87 | } |
| 88 | + | |
| 81 | 89 | } | ... | ... |